# -*- coding: utf-8 -*-
#
# geXtras plugin for gedit
#
# See README for info or go to http://code.google.com/p/gextras/
#
# Copyright (c) 2010-2011, Simon Downes <simon@simondownes.co.uk>
#
# Licensed under the MIT License;
# A copy of the license is included in the LICENSE file accompanying this
# distribution or online at http://www.opensource.org/licenses/mit-license.php

import gedit
import gtk

class PairCompleter():
   
   opening_chars = u'([{"\''
   closing_chars = u')]}"\''
   
   def delete( self, doc ):
      
      # didn't do nuffink guv
      handled = False
      
      # get chars either side of the cursor and reposition iters so we can select both
      start_iter = doc.get_iter_at_mark(doc.get_insert())
      end_iter   = doc.get_iter_at_mark(doc.get_insert())
      
      start_iter.backward_char()
      opening_char = start_iter.get_char()
      closing_char = end_iter.get_char()
      end_iter.forward_char()
      
      # if chars are an opening/closing combination then delete them
      # we could do some fancy algorithm here but does it make it more usable?
      if opening_char in self.opening_chars and closing_char == self.closing_chars[ self.opening_chars.index(opening_char) ]:
         doc.begin_user_action()
         doc.delete(start_iter, end_iter)
         doc.end_user_action()
         handled = True
         
      return handled
   
   def complete( self, doc, char ):
      
      # didn't do nuffink guv
      handled = False
      
      # skip over closing char if doing so would mean that the preceeding char is correctly balanced
      if char in self.closing_chars:
         if self._get_current_char(doc) == char and self._would_balance_pair(doc, char):
            handled = self._advance_cursor(doc)
      
      if not handled and char in self.opening_chars:
         # if selection then enclose it in pair
         if doc.get_has_selection():
            handled = self._enclose_selection(doc, char)
         else:
            handled = self._complete_pair(doc, char)
      
      return handled
   
   def _get_current_char( self, doc ):
      return doc.get_iter_at_mark(doc.get_insert()).get_char()
   
   def _would_balance_pair( self, doc, closing_char ):
      
      opening_char = self.opening_chars[ self.closing_chars.index(closing_char) ]
      text_iter    = doc.get_iter_at_mark(doc.get_insert())
      
      # work backwards through the buffer until we reach a matching opening char or the start of the buffer
      balance = 1
      while balance != 0 and not text_iter.is_start():
         text_iter.backward_char()
         if text_iter.get_char() == opening_char:
            balance -= 1
         elif text_iter.get_char() == closing_char:
            balance += 1
      
      return balance == 0
   
   def _advance_cursor( self, doc ):
      text_iter = doc.get_iter_at_mark(doc.get_insert())
      text_iter.forward_char()
      doc.place_cursor(text_iter)
      return True
   
   def _enclose_selection( self, doc, opening_char ):
    
      closing_char = self.closing_chars[ self.opening_chars.index(opening_char) ]
      
      start = doc.get_insert()
      end   = doc.get_selection_bound()
      
      # handle backwards selections
      if doc.get_iter_at_mark(start).compare(doc.get_iter_at_mark(end)) > 0:
         start, end = end, start
      
      # insert chars
      doc.begin_user_action()
      doc.insert(doc.get_iter_at_mark(start), opening_char)
      doc.insert(doc.get_iter_at_mark(end), closing_char)
      doc.end_user_action()
      
      # move cursor to end of selection
      text_iter = doc.get_iter_at_mark(end)
      doc.place_cursor(text_iter)
      
      return True
   
   def _complete_pair( self, doc, opening_char ):
      
      closing_char = self.closing_chars[ self.opening_chars.index(opening_char) ]
      text_iter    = doc.get_iter_at_mark(doc.get_insert())
      
      # if we're not at the end of the buffer or a line then
      # only close if next character is whitespace
      if not text_iter.is_end() and not text_iter.ends_line():
         char = text_iter.get_char() 
         if not char.isspace():
            return False
      
      # insert the pair
      doc.begin_user_action()
      doc.insert_at_cursor(opening_char + closing_char)
      doc.end_user_action()
      
      # move cursor back to center of pair
      text_iter = doc.get_iter_at_mark(doc.get_insert())
      text_iter.backward_char()
      
      doc.place_cursor(text_iter)
      
      return True
  